#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
统一符号提取器
整合正则提取和AST提取，根据配置自动选择最优方案
"""

import os
from typing import List, Optional
from pathlib import Path

from ..interfaces_module.extractor import ISymbolExtractor, ExtractorResult, SymbolInfo
from ..builtin_config_module.builtin_config import BuiltInConfig
from ..utils_module.logger import ObfuscationLogger
from core.utils_module.language_utils import extension_to_language, SUPPORTED_LANGUAGES


class SymbolExtractor(ISymbolExtractor):
    """
    统一符号提取器
    根据配置和可用性自动选择正则或AST提取
    """

    def __init__(self, config: BuiltInConfig, logger: ObfuscationLogger):
        """
        初始化统一符号提取器

        Args:
            config: 配置对象
            logger: 日志记录器
        """
        self.config = config
        self.logger = logger

        self.extractors = {}
        self._init_extractors()

        # 确定默认提取方法
        self.default_method = self._determine_default_method()
        logger.log_operation("符号提取器初始化", f"默认使用: {self.default_method}")

    def _init_extractors(self):
        """初始化可用的提取器"""
        # 使用新的模块化提取器（通过适配器）
        try:
            # ASTExtractorAdapter 已经在当前文件中定义，会自动创建模块化提取器
            self.extractors['ast'] = ASTExtractorAdapter(self.config, self.logger)
            self.logger.log_operation("提取器加载", "模块化提取器已加载")
            self.extractors['regex'] = RegexExtractorAdapter(self.config, self.logger)
            self.logger.log_operation("提取器加载", "正则提取器已加载")
        except Exception as e:
            self.logger.log_error(f"模块化提取器加载失败: {e}")
            raise

    def _determine_default_method(self) -> str:
        """确定默认提取方法"""
        # 优先使用AST（如果可用）
        if 'ast' in self.extractors:
            return 'ast'
        # 回退到正则
        elif 'regex' in self.extractors:
            return 'regex'
        else:
            raise RuntimeError("没有可用的符号提取器")

    def extract_symbols(self, file_path: str, method: str = None) -> ExtractorResult:
        """
        提取单个文件中的符号

        此方法会根据配置和可用性自动选择最优的提取方法（AST或正则表达式）。
        AST提取更准确但需要编译器支持，正则提取更快但精度略低。

        Args:
            file_path: 文件路径（支持 .swift, .m, .h, .java, .kt 等）
            method: 指定提取方法（'regex'或'ast'），None则使用默认
                - 'ast': 使用编译器AST分析（更准确，需要编译器）
                - 'regex': 使用正则表达式（更快，精度约70-80%）
                - None: 自动选择（优先AST）

        Returns:
            ExtractorResult: 提取结果，包含：
                - symbols: 按类型分类的符号字典
                - errors: 提取过程中的错误列表

        Example:
            >>> extractor = SymbolExtractor(config, logger)
            >>> result = extractor.extract_symbols('UserManager.swift')
            >>> print(f"类: {len(result.symbols.get('classes', []))}")
            >>> print(f"方法: {len(result.symbols.get('methods', []))}")
        """
        # 确定使用的方法
        method = method or self.default_method

        # 获取对应的提取器
        extractor = self.extractors.get(method)
        if not extractor:
            self.logger.log_warning(f"提取方法 {method} 不可用，使用默认方法")
            extractor = self.extractors[self.default_method]

        # 执行提取
        try:
            result = extractor.extract_symbols(file_path)
            self.logger.log_file_processing(
                file_path,
                f"使用{method}提取了 {result.get_all_symbol_names().__len__()} 个符号"
            )
            return result
        except Exception as e:
            # 如果AST失败，尝试回退到正则
            if method == 'ast' and 'regex' in self.extractors:
                self.logger.log_warning(f"AST提取失败: {e}，尝试正则提取")
                return self.extractors['regex'].extract_symbols(file_path)
            else:
                # 返回空结果，记录错误
                result = ExtractorResult(symbols={})
                result.errors.append(f"提取失败: {str(e)}")
                return result

    def extract_symbols_batch(self, file_paths: List[str], method: str = None) -> ExtractorResult:
        """
        批量提取符号

        Args:
            file_paths: 文件路径列表
            method: 指定提取方法（'regex'或'ast'），None则使用默认

        Returns:
            ExtractorResult: 合并后的提取结果

        Example:
            >>> extractor = SymbolExtractor(config, logger)
            >>> result = extractor.extract_symbols_batch(['file1.swift', 'file2.swift'])
            >>> print(f"提取了 {len(result.get_all_symbol_names())} 个符号")
        """
        result = ExtractorResult(symbols={})

        for file_path in file_paths:
            try:
                file_result = self.extract_symbols(file_path, method)
                result.merge(file_result)
            except Exception as e:
                result.errors.append(f"{file_path}: {str(e)}")

        return result

    def supports_language(self, language: str) -> bool:
        """检查是否支持指定语言"""
        # 任何一个提取器支持就算支持
        for extractor in self.extractors.values():
            if extractor.supports_language(language):
                return True
        return False

    def get_supported_languages(self) -> List[str]:
        """获取支持的语言列表"""
        languages = set()
        for extractor in self.extractors.values():
            languages.update(extractor.get_supported_languages())
        return sorted(list(languages))

    def get_best_method_for_file(self, file_path: str) -> str:
        """
        根据文件特征选择最佳提取方法

        Args:
            file_path: 文件路径

        Returns:
            str: 推荐的提取方法
        """
        # 获取文件大小
        try:
            file_size = os.path.getsize(file_path)
        except:
            file_size = 0

        # 大文件优先使用正则（更快）
        if file_size > 1024 * 1024:  # 1MB
            if 'regex' in self.extractors:
                return 'regex'

        # 小文件优先使用AST（更准确）
        if 'ast' in self.extractors:
            return 'ast'

        return self.default_method


class RegexExtractorAdapter(ISymbolExtractor):
    """正则提取器适配器"""

    def __init__(self, config: BuiltInConfig, logger: ObfuscationLogger):
        from core.extractors_module import SwiftExtractor, ObjCExtractor, JavaExtractor, KotlinExtractor

        self.logger = logger
        self.language_extractors = {
            'swift': SwiftExtractor(config, logger),
            'objc': ObjCExtractor(config, logger),
            'java': JavaExtractor(config, logger),
            'kotlin': KotlinExtractor(config, logger),
        }

    def extract_symbols(self, file_path: str) -> ExtractorResult:
        """使用正则或轻量方式提取符号"""
        result = ExtractorResult(symbols={})
        language = extension_to_language(Path(file_path).suffix)

        if not language or language not in self.language_extractors:
            result.errors.append(f"不支持的文件类型: {file_path}")
            return result

        extractor = self.language_extractors[language]

        try:
            if language == 'swift':
                symbol_dict = extractor.extract_symbols_by_regex([file_path])
            else:
                symbol_dict = extractor.extract_symbols_from_file(file_path)

            self._merge_symbol_dict(result, symbol_dict, file_path)
        except Exception as e:
            result.errors.append(f"{file_path}: {str(e)}")

        return result

    def extract_symbols_batch(self, file_paths: List[str]) -> ExtractorResult:
        """批量提取"""
        result = ExtractorResult(symbols={})
        for file_path in file_paths:
            file_result = self.extract_symbols(file_path)
            result.merge(file_result)
        return result

    def supports_language(self, language: str) -> bool:
        """检查语言支持"""
        return language.lower() in SUPPORTED_LANGUAGES

    def get_supported_languages(self) -> List[str]:
        """获取支持的语言"""
        return list(SUPPORTED_LANGUAGES)

    def _merge_symbol_dict(self, result: ExtractorResult, symbol_dict, fallback_file: str):
        if not symbol_dict:
            return

        for symbol_type, symbol_data in symbol_dict.items():
            if symbol_type == 'file_symbols':
                continue

            entries = []
            if isinstance(symbol_data, dict):
                entries = list(symbol_data.values())
            elif isinstance(symbol_data, list):
                entries = symbol_data
            else:
                continue

            for entry in entries:
                symbol_info = self._coerce_symbol_info(entry, symbol_type, fallback_file)
                if symbol_info:
                    result.symbols.setdefault(symbol_type, []).append(symbol_info)

    def _coerce_symbol_info(self, entry, symbol_type: str, fallback_file: str) -> Optional[SymbolInfo]:
        if isinstance(entry, SymbolInfo):
            return entry

        if isinstance(entry, dict):
            name = entry.get('name')
            if not name:
                return None
            return SymbolInfo(
                name=name,
                type=entry.get('type', symbol_type),
                file=entry.get('file') or entry.get('file_path', fallback_file),
                line=entry.get('line', entry.get('line_number', 0)),
                column=entry.get('column', entry.get('column_number', 0)),
                modifiers=entry.get('modifiers', []),
                parent=entry.get('parent')
            )

        if isinstance(entry, str) and entry:
            inferred_type = symbol_type or self._guess_symbol_type(entry)
            return SymbolInfo(
                name=entry,
                type=inferred_type,
                file=fallback_file,
                line=0,
                column=0
            )

        return None

    def _guess_symbol_type(self, symbol_name: str) -> str:
        """根据符号名称猜测类型"""
        if not symbol_name:
            return 'unknown'
        if symbol_name[0].isupper():
            return 'class'
        if '(' in symbol_name or symbol_name.startswith(('func', 'def')):
            return 'method'
        return 'property'


class ASTExtractorAdapter(ISymbolExtractor):
    """
    AST提取器适配器

    使用新的模块化语言提取器。
    将语言特定的提取器适配到统一的 ISymbolExtractor 接口。

    优势：
    1. 统一接口：符合 ISymbolExtractor 规范
    2. 模块化：每种语言独立维护
    3. 自动路由：根据文件扩展名自动选择对应的语言提取器
    """

    def __init__(self, config, logger):
        """
        初始化适配器

        Args:
            config: BuiltInConfig 配置对象
            logger: 日志记录器
        """
        from core.extractors_module import SwiftExtractor, ObjCExtractor, JavaExtractor, KotlinExtractor

        # 创建各语言提取器
        self.use_legacy = False  # 默认使用模块化提取器实现
        self.language_extractors = {
            'swift': SwiftExtractor(config, logger),
            'objc': ObjCExtractor(config, logger),
            'java': JavaExtractor(config, logger),
            'kotlin': KotlinExtractor(config, logger),
        }

    def extract_symbols(self, file_path: str) -> ExtractorResult:
        """适配提取方法"""
        result = ExtractorResult(symbols={})

        try:
            # 判断文件语言
            ext = Path(file_path).suffix.lower()
            language = self._detect_language(ext)

            if not language or language not in self.language_extractors:
                result.errors.append(f"不支持的文件类型: {ext}")
                return result

            # 使用模块化提取器
            extractor = self.language_extractors[language]
            ast_result = extractor.extract_symbols_from_file(file_path)

            # 转换AST结果到统一格式
            # AST提取器返回格式: {'classes': [names], 'methods': [names], ...}
            if ast_result:
                for symbol_type, symbol_data in ast_result.items():
                    if symbol_type not in result.symbols:
                        result.symbols[symbol_type] = []

                    # 检查是字典还是列表
                    if isinstance(symbol_data, dict):
                        # 字典格式: {name: data}
                        for symbol_name, data in symbol_data.items():
                            if isinstance(data, SymbolInfo):
                                result.symbols[symbol_type].append(data)
                                continue

                            location = data.get('location', {}) if isinstance(data, dict) else {}
                            file_path_value = file_path
                            line_value = 0
                            column_value = 0

                            if isinstance(data, dict):
                                file_path_value = data.get('file') or data.get('file_path', file_path)
                                line_value = data.get('line')
                                if line_value is None:
                                    line_value = data.get('line_number', 0)
                                column_value = data.get('column')
                                if column_value is None:
                                    column_value = data.get('column_number', 0)

                                if isinstance(location, dict):
                                    line_value = location.get('line', line_value)
                                    column_value = location.get('column', column_value)

                            symbol_info = SymbolInfo(
                                name=symbol_name,
                                type=symbol_type,
                                file=file_path_value,
                                line=line_value,
                                column=column_value,
                                modifiers=data.get('modifiers', []) if isinstance(data, dict) else [],
                                parent=data.get('parent') if isinstance(data, dict) else None
                            )
                            result.symbols[symbol_type].append(symbol_info)
                    elif isinstance(symbol_data, list):
                        # 列表格式: [names]  ← 单文件提取返回这种格式
                        for symbol_name in symbol_data:
                            symbol_info = SymbolInfo(
                                name=symbol_name,
                                type=symbol_type,
                                file=file_path,
                                line=0,
                                column=0
                            )
                            result.symbols[symbol_type].append(symbol_info)

        except Exception as e:
            result.errors.append(str(e))

        return result

    def _detect_language(self, ext: str) -> Optional[str]:
        """
        根据文件扩展名检测语言

        Args:
            ext: 文件扩展名（小写，带点）

        Returns:
            语言名称 ('swift', 'objc', 'java', 'kotlin') 或 None
        """
        return extension_to_language(ext)

    def extract_symbols_batch(self, file_paths: List[str]) -> ExtractorResult:
        """批量提取，利用AST的批处理能力"""
        # 按语言分组
        files_by_language = {language: [] for language in SUPPORTED_LANGUAGES}

        for file_path in file_paths:
            ext = Path(file_path).suffix
            language = self._detect_language(ext)
            if language:
                files_by_language.setdefault(language, []).append(file_path)

        result = ExtractorResult(symbols={})

        # 批量处理各语言文件
        for language, files in files_by_language.items():
            if files:
                lang_result = self._process_language_files(files, language)
                result.merge(lang_result)

        return result

    def _process_language_files(self, files: List[str], language: str) -> ExtractorResult:
        """处理特定语言的文件"""
        result = ExtractorResult(symbols={})

        if not self.use_legacy:
            # 使用新的模块化提取器的批量处理
            if language in self.language_extractors:
                extractor = self.language_extractors[language]
                ast_result = extractor.extract_symbols_batch(files)

                # 转换结果
                if ast_result:
                    for symbol_type, symbol_data in ast_result.items():
                        if symbol_type == 'file_symbols':
                            continue

                        if symbol_type not in result.symbols:
                            result.symbols[symbol_type] = []

                        if isinstance(symbol_data, dict):
                            for symbol_name, data in symbol_data.items():
                                if isinstance(data, SymbolInfo):
                                    result.symbols[symbol_type].append(data)
                                elif isinstance(data, dict):
                                    file_path = data.get('file') or data.get('file_path', '')
                                    line = data.get('line', data.get('line_number', 0))
                                    column = data.get('column', data.get('column_number', 0))
                                    symbol_info = SymbolInfo(
                                        name=symbol_name,
                                        type=symbol_type,
                                        file=file_path,
                                        line=line,
                                        column=column,
                                        modifiers=data.get('modifiers', []),
                                        parent=data.get('parent')
                                    )
                                    result.symbols[symbol_type].append(symbol_info)
        else:
            # 使用旧的方式
            for file_path in files:
                file_result = self.extract_symbols(file_path)
                result.merge(file_result)

        return result

    def supports_language(self, language: str) -> bool:
        """检查语言支持"""
        return language.lower() in SUPPORTED_LANGUAGES

    def get_supported_languages(self) -> List[str]:
        """获取支持的语言"""
        return list(SUPPORTED_LANGUAGES)
